5W - 이스티오 JWT 인증

개요

이전 노트에서는 인증서에 기반한 신원 인증 세팅을 알아봤다.
그런데 이스티오에서는 이것 말고도 HTTP의 Authorization 헤더를 이용해 신원을 인증하는 방법도 존재한다.
이것에 관련된 리소스는 RequestAuthentication으로, 이번에는 이걸 파본다.

사전 지식

HTTP의 헤더를 이용한 통신은 흔히 엔드 유저 통신이라고 이야기한다.
엔드 유저의 통신에 대한 인증은 Istio RequestAuthentication 리소스를 통해 이뤄진다.
이건 HTTP 레벨의 인증 수단을 활용하는데, 즉 Authorization 헤더를 이용한다는 말이다.
서비스 간 mtls 통신을 하게 설정을 해서 신원을 제공하는 거랑 별개로 해당 http 요청의 신원 자체는 또 따로 제공할 수 있다.
예를 들어, Mtls 통신을 하며 얻게 된 신원과 요청 헤더에서 토큰 값을 읽어서 얻게 된 신원은 공존할 수 있다.
물론 서비스 메시 외부에서 오는 요청을 인증하기 위해서는 이 리소스밖에 방법이 없다.

구체적으로는 JWT을 읽어서 인증에 활용하며, 다양한 다른 인증 제공자와 결합되도록 설정하는 것이 가능하다.
참고로 이 리소스에선 JWT를 검증하나 아예 해당 값이 없이 들어오는 트래픽이 있다면 익명으로 간주한다.
즉 인증 실패에 대해 보통 401에러를 반환하지만 그냥 대놓고 아무것도 없이 오는 요청은 허용해버린다는 것..
이걸 막는 것은 인가 단계에서 이뤄져야 한다.

JWT

JSON Web Token.
흔히 HTTP 통신 간 신원을 제공하기 위해 사용되는 토큰으로, RFC 7519에 정의됐다.
가장 흔하게 사용되는 영역이 OAuth, OIDC 인데, JWT라는 토큰 형식이 이들의 기반이 되어준다고 이해하면 좋다.
이름의 유래가 굉장히 단순한데, 그냥 웹 환경에서 JSON 형식으로 만들어진 토큰이라서 JWT이다.
image.png
JWT 토큰은 위와 같이 세 부분으로 나뉘며, 각 부분은 base64로 인코딩된다.[1]
일단 헤더와 페이로드가 있는데, 그 값들을 합쳐서 개인키로 암호화한 값을 서명 부분에 달아둔다.
이 토큰을 받은 대상은 공개키로 서명 부분은 복호화해보고 헤더와 내용이 위변조됐는지 확인할 수 있다.
즉, JWT는 어떠한 내용을 담고, 이 내용이 신뢰되는 주체로부터 서명 받은 것을 인증하는데 가장 큰 목적을 둔다.
헤더는 그냥 암호화 방식이랑 유형을 간단하게 나타내고 실제 내용은 페이로드에 전부 들어간다고 보면 된다.

Payload

페이로드 부분에는 임의의 내용을 때려박을 수 있는데, 이때 각 필드를 claim이라고 부른다.
이 토큰이 어떤 내용을 담고 있다, 주장(claim)을 하는 것이기 때문에 이리 부른다고 한다(gpt 피셜).
그래도 조금 RFC로 규정된 형태의 필드들이 있긴 하다.

뭐.. 이렇긴 한데 사실 이거 다 쓰는 토큰을 거의 본 적이 없다.
그리고 이거 말고도 임의의 클레임을 넣어도 상관이 없어서 그걸 활용하는 케이스가 대부분이기도 하다.
표준이지만 너무 나이브한 표준인

JWE? JWS? JWKS?

JWT와 관련해서 나오는 몇 가지 개념들이 추가적으로 있다.
사실 JWT는 이런 식으로 만든 토큰 인터페이스라고 보면 된다.
이걸 실제 토큰으로 구현할 때는 두 가지 방식이 존재한다.

그러니 흔히 JWT라고 하면 JWS 형식이라고 보면 되겠다.

그렇다면 JWKS는 무엇인가?

https://<server_domain>/.well-known/jwks.json

JSON Web Key Set은 서명된 값을 확인하는 데 사용되는 키 집합을 말한다.
받은 JWT토큰을 검증하기 위해서는 암호화할 때 사용된 몇 가지 값을 알아야 한다.
그래서 보통 암호화를 한 주체는 해당 값들을 알 수 있도록 위의 경로를 노출한다.

{
  "keys": [
    {
      "kty": "EC",
      "crv": "P-256",
      "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
      "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
      "use": "enc",
      "kid": "1"
    },
    {
      "kty": "RSA",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
      "e": "AQAB",
      "alg": "RS256",
      "kid": "2011-04-29"
    }
  ]
}

대체로 위 경로에는 이런 식으로 값이 담겨 있으며, 이를 기반으로 JWT를 검증하려는 측은 위변조 유무를 파악할 수 있다.[3]

RequestAuthentication

위에서 간략히 말했듯이, Istio PeerAuthentication는 서비스 간 인증을 다루지만 사실 구체적으로 들어가면 그냥 통신 간 암호화에 대한 설정이 주를 이룰 뿐이다.
반면 RequestAuthentication은 TLS가 아니라 실제 통신 내용에서 신원 인증을 하는 리소스이다.
기본적으로 JWT의 값을 기반으로 인증을 수행하며, 인증되지 않는다면 401에러를 반환시킨다.
주의할 점으로, JWT를 안 가져온 요청에 대해서는 익명 요청으로 간주하고 에러를 반환하지 않는다는 것.
익명 요청을 처리하는 것은 인가 단계에서 이뤄져야 한다.

이 놈도 워크로드 - 네임스페이스 - 전역 범위 정책 순을 따른다.

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: httpbin
  namespace: foo
spec:
  selector:
    matchLabels:
      app: httpbin
  jwtRules:
  - issuer: "issuer-foo"
    jwksUri: https://example.com/.well-known/jwks.json

selector말고 targetRef를 통해 리소스를 명시적으로 지정하는 것도 가능하다.
jwtRules 쪽에 jwt 토큰에 들어갈 각종 설정들을 넣는다.
어디까지나 이 리소스는 인증을 위한 리소스임을 명심하자.
여기 설정에는 그냥 해당 jwt 토큰이 신뢰될 만한지 검증하는 내용만 들어간다.

간단하게 필드를 정리하자면,

할 수 있는 설정이 굉장히 제한적인데, 어디까지나 신원 인증을 하기 위한 설정을 넣는 리소스라 그렇다.

실습 진행

그럼 이제 이 리소스를 활용해 인증을 하는 실습을 해본다.
이걸 기반으로 트래픽을 허용할지 말지 인가하는 실습은 다음 노트에서 진행한다.

kubectl -n istio-system delete -f ch9/meshwide-strict-peer-authn.yaml
kubectl -n istioinaction delete -f ch9/workload-permissive-peer-authn.yaml

참고로 이전 실습에서 활용했던 PeerAuthentication 리소스는 지워둬야 조금 더 편하게 실습할 수 있다.

jwtd () {
  local input="${1:-}"
  if [ -z "$input" ]; then
    if [ ! -t 0 ]; then
      input=$(cat /dev/stdin)
    else
      echo >&2 '✗ Need an argument or have a piped input!'
      return 1
    fi
  fi
  echo "$input" \
    | jq -Rrce 'split(".")[1] | . + "=" * (. | 4 - length % 4)' \
    | openssl base64 -d -A \
    | jq .
}

간단하게 jwt의 페이로드 부분만 뜯는 스크립트를 이용하자.[4]

jwt 기반 사용자 인가해보기

apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "jwt-token-request-authn"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  jwtRules:
  - issuer: "auth@istioinaction.io"
    jwks: |
      { "keys":[ {"e":"AQAB","kid":"CU-ADJJEbH9bXl0tpsQWYuo4EwlkxFUHbeJ4ckkakCM","kty":"RSA","n":"zl9VRDbmVvyXNdyoGJ5uhuTSRA2653KHEi3XqITfJISvedYHVNGoZZxUCoiSEumxqrPY_Du7IMKzmT4bAuPnEalbY8rafuJNXnxVmqjTrQovPIerkGW5h59iUXIz6vCznO7F61RvJsUEyw5X291-3Z3r-9RcQD9sYy7-8fTNmcXcdG_nNgYCnduZUJ3vFVhmQCwHFG1idwni8PJo9NH6aTZ3mN730S6Y1g_lJfObju7lwYWT8j2Sjrwt6EES55oGimkZHzktKjDYjRx1rN4dJ5PR5zhlQ4kORWg1PtllWy1s5TSpOUv84OPjEohEoOWH0-g238zIOYA83gozgbJfmQ"}]}

예제 실습에 사용할 토큰을 검증할 수 있는 jwks를 담은 RequestAuthentication 리소스이다.
인그레스 게이트웨이에 이 리소스를 적용하여 들어온 요청에 신원을 부여해보자.

istioctl pc listener -n istio-system istio-ingressgateway-b9d758b7c-t2hpj --port 8080 -ojson | fx

image.png
이 설정은 http 필터에서 jwt_authn으로 들어간다.

고맙게도 책 실습에서 활용할 모든 jwt 토큰을 미리 제공해주고 있다.

cat ch9/enduser/user.jwt | jwtd

image.png
이건 유효한 토큰이다.
이걸 기반으로 먼저 요청을 날려본다.

USER_TOKEN=$(< ch9/enduser/user.jwt)
curl -K curl.conf \
	-H "Authorization: Bearer $USER_TOKEN" \
     -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

image.png
해당 요청은 간단하게 성공한다.

그렇다면 발급자 정보가 잘못된 jwt는 어떨까?
image.png

WRONG_ISSUER=$(< ch9/enduser/not-configured-issuer.jwt)
curl -K curl.conf \
	-H "Authorization: Bearer $WRONG_ISSUER" \
     -sSl -o /dev/null -w "%{http_code}" webapp.istioinaction.io:30000/api/catalog

image.png
바로 인증 실패를 띄우는 것을 확인할 수 있다.

kubectl logs -n istio-system -l app=istio-ingressgateway  

image.png
게이트웨이 로그에도 issuer가 설정되지 않아서 거부했다는 문구가 남는다.

미인증 요청 거부하기

image.png
그러나 사실 그냥 아무런 토큰을 보내지 않으면 요청은 잘만 수행된다.
그저 미인증된 트래픽으로서 통과될 뿐이다.
이런 트래픽을 막는 역할은 AuthorizationPolicy의 역할이다.
다음 노트에서 다룰 것이지만, 여기에서 간단하게 미인증 요청을 거부하는 것도 세팅해본다.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: app-gw-requires-jwt
  namespace: istio-system
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"] 

인가 정책에 source 필드는 신원이 제공될 때만 채워진다.
미인증된 트래픽이라면 RequestPrincipals가 비어있을 것이기에, 해당 부분이 채워지지(*) 않은(not) 모든 요청을 거부하도록 세팅한다.
image.png
이제 미인증된 유저의 요청은 권한 없음으로 거부되는 것을 확인할 수 있다.

istioctl proxy-config listener deploy/istio-ingressgateway.istio-system --port 8080 -o json | fx

image.png
엔보이에 적용된 설정을 보면 authn 필터 아래 바로 RBAC 필터가 들어간 것을 볼 수 있다.
위 필터에서 적용된 정보들은 필터 메타데이터로 저장되어 아래 필터에서 활용될 것이다.

결론

이스티오 자체적으로 JWT 인증에 대한 설정을 할 수 있기 때문에, 이스티오를 잘만 활용하면 회원관리에 대한 로직을 직접 개발하지 않아도 어느 정도 인증 인가 관리를 할 수 있게 된다.
여태까지 본 리소스는 그냥 인증에 대한 설정만 담고 있었고, 실제로 해당 신원을 바탕으로 요청을 허용하고 거부하는 설정은 AuthorizationPolicy를 통해 해야만 한다.

이전 글, 다음 글

다른 글 보기

이름 index noteType created
1W - 서비스 메시와 이스티오 1 published 2025-04-10
1W - 간단한 장애 상황 구현 후 대응 실습 2 published 2025-04-10
1W - Gateway API를 활용한 설정 3 published 2025-04-10
1W - 네이티브 사이드카 컨테이너 이용 4 published 2025-04-10
2W - 엔보이 5 published 2025-04-19
2W - 인그레스 게이트웨이 실습 6 published 2025-04-17
3W - 버츄얼 서비스를 활용한 기본 트래픽 관리 7 published 2025-04-22
3W - 트래픽 가중치 - flagger와 argo rollout을 이용한 점진적 배포 8 published 2025-04-22
3W - 트래픽 미러링 패킷 캡쳐 9 published 2025-04-22
3W - 서비스 엔트리와 이그레스 게이트웨이 10 published 2025-04-22
3W - 데스티네이션 룰을 활용한 네트워크 복원력 11 published 2025-04-26
3W - 타임아웃, 재시도를 활용한 네트워크 복원력 12 published 2025-04-26
4W - 이스티오 메트릭 확인 13 published 2025-05-03
4W - 이스티오 메트릭 커스텀, 프로메테우스와 그라파나 14 published 2025-05-03
4W - 오픈텔레메트리 기반 트레이싱 예거 시각화, 키알리 시각화 15 published 2025-05-03
4W - 번외 - 트레이싱용 심플 메시 서버 개발 16 published 2025-05-03
5W - 이스티오 mTLS와 SPIFFE 17 published 2025-05-11
5W - 이스티오 JWT 인증 18 published 2025-05-11
5W - 이스티오 인가 정책 설정 19 published 2025-05-11
6W - 이스티오 설정 트러블슈팅 20 published 2025-05-18
6W - 이스티오 컨트롤 플레인 성능 최적화 21 published 2025-05-18
8W - 가상머신 통합하기 22 published 2025-06-01
8W - 엔보이와 iptables 뜯어먹기 23 published 2025-06-01
9W - 앰비언트 모드 구조, 원리 24 published 2025-06-07
9W - 앰비언트 헬름 설치, 각종 리소스 실습 25 published 2025-06-07
7W - 이스티오 메시 스케일링 26 published 2025-06-09
7W - 엔보이 필터를 통한 기능 확장 27 published 2025-06-09

관련 문서

이름 noteType created
RBAC knowledge 2025-01-19
쿠버 RBAC knowledge 2024-09-02
Keycloak knowledge 2025-01-27
Istio AuthorizationPolicy knowledge 2025-05-04
OAuth knowledge 2025-05-09
6W - api 구조와 보안 1 - 인증 published 2025-03-15
6W - api 보안 2 - 인가, 어드미션 제어 published 2025-03-16
T-각 네임스페이스 서비스 어카운트에 권한 바인딩 topic/temp 2025-03-16

참고


  1. https://inpa.tistory.com/entry/WEB-📚-JWTjson-web-token-란-💯-정리 ↩︎

  2. https://auth-wiki.logto.io/ko/jwe ↩︎

  3. https://blog.advenoh.pe.kr/jwks-json-web-key-set이란/ ↩︎

  4. https://gist.github.com/angelo-v/e0208a18d455e2e6ea3c40ad637aac53?permalink_comment_id=5119116#gistcomment-5119116 ↩︎